Skip to content

feat(vnext): coordinate catalog epochs#193

Merged
Light2Dark merged 4 commits into
dev-refactorfrom
codex/vnext-catalog-epoch-coordinator
Jul 25, 2026
Merged

feat(vnext): coordinate catalog epochs#193
Light2Dark merged 4 commits into
dev-refactorfrom
codex/vnext-catalog-epoch-coordinator

Conversation

@Light2Dark

@Light2Dark Light2Dark commented Jul 25, 2026

Copy link
Copy Markdown
Member

Part of #169.

  • Add a package-private provider/scope epoch gate so catalog responses and invalidations cannot publish against mixed catalog state.
  • Share one subscription per scope with two-phase membership activation, single-use authenticated captures, monotonic arbitration, and exact revision fan-out.
  • Bound callback storms, FIFO admissions, memberships, scopes, and reentrant provider cleanup with fail-closed quarantine.
  • Retire failed targets atomically and sever provider/session closure graphs before external cleanup or retained-handle disposal.
  • Record the lifecycle, ordering, and resource contracts in ADR 0005.

Summary by cubic

Introduces a catalog epoch coordinator that gates responses and invalidations per scope to prevent mixed catalog state and ensure deterministic revision fan‑out, and isolates hostile cleanup promises. Part of #169.

  • New Features

    • One subscription per scope with two‑phase membership and authenticated, single‑use epoch captures.
    • Monotonic arbitration and exact revision fan‑out across sessions sharing a scope.
    • Bounds for callback storms and cleanups, FIFO admissions, and fail‑closed quarantine for misbehavior.
    • Drains async provider/session cleanup settlement, isolates hostile cleanup promises, and atomically retires failed targets with reentrant‑safe cleanup.
    • Updated ADR‑0005 to document lifecycle and ordering; added extensive tests and a benchmark with bench:catalog-coordinator.
  • Migration

    • SqlRelationCatalogProvider.subscribe now returns a this‑free cleanup function SqlCatalogSubscriptionCleanup (may be async) instead of a SqlDisposable; update providers to return () => void | PromiseLike<void>.
    • SqlDisposable.dispose is now this‑free: change to dispose(this: void): void and avoid using this inside dispose.

Written for commit 3d66bd4. Summary will update on new commits.

Review in cubic

@Light2Dark
Light2Dark marked this pull request as ready for review July 25, 2026 06:28
@Light2Dark
Light2Dark requested a review from Copilot July 25, 2026 06:28
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 98.75% (🎯 91%) 395 / 400
🔵 Statements 98.1% (🎯 91%) 414 / 422
🔵 Functions 98.14% (🎯 90%) 53 / 54
🔵 Branches 96.99% (🎯 85%) 226 / 233
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/vnext/relation-catalog-epoch-coordinator.ts 98.1% 96.99% 98.14% 98.75% 228, 341, 371-372, 404, 518, 912, 956
src/vnext/relation-completion-types.ts 0% 0% 0% 0%
Generated in workflow #742 for commit 3d66bd4 by the Vitest Coverage Report Action

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a vnext “catalog epoch coordinator” that serializes and arbitrates catalog invalidations and response epochs per scope, preventing mixed catalog state and providing bounded, fail-closed behavior under hostile/reentrant providers.

Changes:

  • Adds createSqlCatalogEpochCoordinator and related types/constants to coordinate scope memberships, epoch captures, invalidations, and response submissions with bounded FIFO draining.
  • Refines SqlDisposable.dispose to be a this-free callback ((this: void) => void) and updates type-level tests to enforce it.
  • Adds extensive vitest coverage plus a vitest benchmark and a new bench:catalog-coordinator npm script; updates ADR 0005 with lifecycle/ordering/resource-contract details.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/vnext-types/marimo-relation-completion.test-d.ts Adds type-level assertion that SqlDisposable.dispose cannot be receiver-dependent.
src/vnext/relation-completion-types.ts Updates SqlDisposable.dispose signature to (this: void) => void.
src/vnext/relation-catalog-epoch-coordinator.ts New epoch coordinator implementation with bounded command draining, scope subscriptions, capture authentication, and fail-closed cleanup/quarantine behavior.
src/vnext/tests/relation-catalog-epoch-coordinator.test.ts Comprehensive test suite covering ordering, hostile behaviors, bounds, and disposal semantics.
src/vnext/tests/relation-catalog-epoch-coordinator.bench.ts Adds benchmarks and preflight assertions for coordinator fan-out, churn, and callback storm behavior.
package.json Adds bench:catalog-coordinator script entry.
docs/adr/0005-parser-independent-relation-completion.md Documents the coordinator’s lifecycle, ordering, and resource limits in ADR 0005.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/vnext/relation-catalog-epoch-coordinator.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/vnext/relation-catalog-epoch-coordinator.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/vnext/relation-catalog-epoch-coordinator.ts">

<violation number="1" location="src/vnext/relation-catalog-epoch-coordinator.ts:381">
P2: A hostile rejected native cleanup Promise can still escape as an unhandled rejection when it shadows `then`, because this assimilation path does not attach a handler to the original Promise. The cleanup isolation should use an intrinsic native-Promise reaction for native Promises (including poisoned own `then` properties) before falling back to thenable assimilation.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

try {
const result = Reflect.apply(cleanup, undefined, []);
const settlement = new Promise<unknown>((resolve) => {
resolve(result);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A hostile rejected native cleanup Promise can still escape as an unhandled rejection when it shadows then, because this assimilation path does not attach a handler to the original Promise. The cleanup isolation should use an intrinsic native-Promise reaction for native Promises (including poisoned own then properties) before falling back to thenable assimilation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/vnext/relation-catalog-epoch-coordinator.ts, line 381:

<comment>A hostile rejected native cleanup Promise can still escape as an unhandled rejection when it shadows `then`, because this assimilation path does not attach a handler to the original Promise. The cleanup isolation should use an intrinsic native-Promise reaction for native Promises (including poisoned own `then` properties) before falling back to thenable assimilation.</comment>

<file context>
@@ -375,7 +376,11 @@ function cleanupSubscription(subscription: SubscriptionState): void {
-    Reflect.apply(cleanup, undefined, []);
+    const result = Reflect.apply(cleanup, undefined, []);
+    const settlement = new Promise<unknown>((resolve) => {
+      resolve(result);
+    });
+    void settlement.then(undefined, IGNORE_CLEANUP_REJECTION);
</file context>

@Light2Dark
Light2Dark merged commit ffa8229 into dev-refactor Jul 25, 2026
9 checks passed
@Light2Dark
Light2Dark deleted the codex/vnext-catalog-epoch-coordinator branch July 25, 2026 07:00
Light2Dark added a commit that referenced this pull request Jul 25, 2026
Part of #169. Follows #193.

- Add a package-private synchronous epoch-transition hook so shared
catalog work can retire before session revision listeners observe an
accepted epoch.
- Preserve deterministic prepare/commit/dispatch ordering for provider
invalidations and higher response epochs.
- Tighten provider cleanup and transition dispatch to an exact
`undefined` contract, with fail-closed quarantine for invalid returns.
- Dispose before inspecting invalid thenables, closing the hostile
getter reentrancy window identified after #193 merged.
- Keep the no-hook and null-transition paths allocation-free and add a
configured-hook storm benchmark.
- Extend ADR 0005, type fixtures, lifecycle tests, and adversarial
Promise/thenable coverage.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a package-private, synchronous epoch-transition hook that runs
after epoch install to retire catalog work before session listeners.
Enforces exact-return contracts and fail-closed handling to harden
reentrancy, cleanup, and poisoned-thenable edge cases; supports #169.

- **New Features**
- Added `SqlCatalogEpochTransitionTarget` that may return a dispatch
closure or `null`; dispatch must return exactly `undefined` (sync only).
- Preserved strict ordering: transition-prepare → revision-prepare →
producer settle → transition-dispatch → revision-dispatch; reentrant
dispatch queues behind current dispatch.
- Snapshots the revision audience before transition; producers/aborts
see prepared revisions; listeners see already-retired work.
- Rejects non-function transition targets during coordinator creation
(`reason: "invalid-transition-target"`); any thrown preparation, thrown
dispatch, or non-`undefined` return disposes fail-closed and discards
response decisions.
- Drains detached Promise/thenable results for cleanup and transition
returns after disposal, avoiding hostile getter reentrancy; no-hook and
`null`-dispatch paths allocate nothing. Updated ADR 0005, added a
configured-hook storm benchmark, and expanded adversarial tests.

- **Migration**
- Update `SqlCatalogSubscriptionCleanup` to be synchronous and return
`undefined` only (no Promises or other return values).

<sup>Written for commit 9d7c9c3.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/194?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants